#region File information

/**
 * File: Camera.cs
 * Date: 08-03-2008
 * Auth: Jan-Jaap Severs
 * 
 * Desc: A simple camera class, exposing some default properties like view and projection matrices, position and
 *       lookat.
 */

#endregion

#region Using statements

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

#endregion

namespace Gremlin.Components
{
    /// <summary>
    /// A simple camera class, exposing some default properties like view and projection matrices, position and
    /// lookat.
    /// </summary>
    public class Camera : Microsoft.Xna.Framework.GameComponent
    {
        #region Member variables

        protected Matrix _viewMatrix;
        protected Matrix _projMatrix;

        protected Vector3 _position;
        protected Vector3 _lookAt;
        protected Vector3 _viewingDirection;
        protected Vector3 _cameraRotation;

        protected BoundingFrustum _frustum;

        protected float _aspectRatio;
        protected float _FOV;
        protected float _nearClip;
        protected float _farClip;

        #endregion

        /// <summary>
        /// Creates a new Camera object.
        /// </summary>
        /// <param name="game">The instance of the Game class this Camera gets registered to.</param>
        public Camera(Microsoft.Xna.Framework.Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Initializes the Camera, setting default values for all variables.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            _viewMatrix = Matrix.Identity;

            _position = new Vector3(0.0f, 30.0f, 30.0f);
            _viewingDirection = new Vector3(0.0f, 0.0f, -1.0f);
            _lookAt = _position + _viewingDirection;
            _cameraRotation = Vector3.Zero;

            int screenWidth = Game.Window.ClientBounds.Width;
            int screenHeight = Game.Window.ClientBounds.Height;
            _aspectRatio = (float)screenWidth / (float)screenHeight;

            _FOV = MathHelper.PiOver4;

            _nearClip = 0.1f;
            _farClip = 10000.0f;

            // Add this camera to the Game's CameraProvider service.
            CameraProvider cameraProvider = (CameraProvider)Game.Services.GetService(typeof(ICameraService));
            cameraProvider.AddCamera(this);
        }

        /// <summary>
        /// Updates the Camera every tick, calculating view and projection matrices.
        /// </summary>
        /// <param name="gameTime">Snapshot of the game timing state.</param>
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            Matrix rotX = Matrix.CreateRotationX(_cameraRotation.X);
            Matrix rotY = Matrix.CreateRotationY(_cameraRotation.Y);
            Matrix rot = rotX * rotY;

            Vector3 transformedDir = Vector3.Transform(_viewingDirection, rot);

            _lookAt = _position + transformedDir;

            _viewMatrix = Matrix.CreateLookAt(_position, _lookAt, new Vector3(0.0f, 1.0f, 0.0f));
            _projMatrix = Matrix.CreatePerspectiveFieldOfView(_FOV, _aspectRatio, _nearClip, _farClip);

            _frustum = new BoundingFrustum(_viewMatrix * _projMatrix);
        }

        /// <summary>
        /// Rotates the camera around the X axis.
        /// </summary>
        /// <param name="radians">The angle to rotate the camera.</param>
        public void RotateX(float radians)
        {
            _cameraRotation.X += radians;
            if (_cameraRotation.X >= 2 * MathHelper.Pi)
                _cameraRotation.X -= 2 * MathHelper.Pi;
        }

        /// <summary>
        /// Rotates the camera around the Y axis.
        /// </summary>
        /// <param name="radians">The angle to rotate the camera.</param>
        public void RotateY(float radians)
        {
            _cameraRotation.Y += radians;
            if (_cameraRotation.Y >= 2 * MathHelper.Pi)
                _cameraRotation.Y -= 2 * MathHelper.Pi;
        }

        /// <summary>
        /// Translates the camera in three directions.
        /// </summary>
        /// <param name="delta">The amount to translate.</param>
        public void Translate(Vector3 delta)
        {
            _position += delta;
        }

        #region Accessors & actuators

        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public Matrix ViewMatrix
        {
            get { return _viewMatrix; }
        }

        public Matrix ProjMatrix
        {
            get { return _projMatrix; }
        }

        public BoundingFrustum Frustum
        {
            get { return _frustum; }
        }

        public Vector3 ViewingDirection
        {
            get { return _lookAt - _position; }
        }

        #endregion
    }

    /// <summary>
    /// A service interface for exposing the Camera object to Game components.
    /// </summary>
    public interface ICameraService
    {
        #region Accessors & actuators

        Camera Camera { get; }

        #endregion
    }

    /// <summary>
    /// A class that implements the ICameraService interface and exposes the Camera object.
    /// </summary>
    public class CameraProvider : ICameraService
    {
        #region Member variables

        private Camera _camera;

        #endregion

        /// <summary>
        /// Store the Camera object instance and make it available for retrieval.
        /// </summary>
        /// <param name="camera"></param>
        public void AddCamera(Camera camera)
        {
            _camera = camera;
        }

        #region Accessors & actuators

        public Camera Camera
        {
            get { return _camera; }
        }

        #endregion
    }
}
